﻿using System;

namespace Cpu6502.Opcodes
{
    /// <summary>
    /// ADC #AA - Adds passed value to Accumulator
    /// </summary>
    class ADC_I : Opcode
    {
        ADCUtility helper = null;

        public ADC_I()
        {
            helper = new ADCUtility();
            this.CpuCycles = 2;
            this.AddressType = CPU.AddressType.Immediate;
        }

        public override void Execute()
        {           
            helper.Argument = (Int16)this.Argument;
            helper.Execute();
        }
    }

    /// <summary>
    /// ADC $AA - ADD using ZeroPage Addressing
    /// </summary>
    class ADC_ZP : Opcode
    {
        ADCUtility helper = null;

        public ADC_ZP()
        {
            helper = new ADCUtility();            
            this.CpuCycles = 3;
            this.AddressType = CPU.AddressType.ZeroPage;
        }

        public override void Execute()
        {            
            helper.Argument =
                CPU.Memory
                [
                    MemoryHelper.GetAddress
                    ((byte)this.Argument, 
                    CPU.AddressType.ZeroPage)
                ];
                        
            helper.Execute();
        }
    }

    /// <summary>
    /// ADC $AA, X - ADD using ZeroPage X Addressing
    /// </summary>
    class ADC_ZP_X : Opcode
    {
        ADCUtility helper = null;

        public ADC_ZP_X()
        {
            helper = new ADCUtility();
            this.CpuCycles = 4;
            this.AddressType = CPU.AddressType.ZeroPageIndexedX;
        }

        public override void Execute()
        {
            helper.Argument =
                CPU.Memory
                [
                    MemoryHelper.GetAddress
                    ((byte)this.Argument,
                    CPU.AddressType.ZeroPageIndexedX)
                ];

            helper.Execute();
        }
    }

    /// <summary>
    /// ADC $AAAA - ADD using Absolute Addressing
    /// </summary>
    class ADC_A : Opcode
    {
        ADCUtility helper = null;

        public ADC_A()
        {
            helper = new ADCUtility();
            this.CpuCycles = 4;
            this.AddressType = CPU.AddressType.Absolute;
        }

        public override void Execute()
        {
            helper.Argument =
                CPU.Memory
                [
                    MemoryHelper.GetAddress
                    ((Int16)this.Argument,
                    CPU.AddressType.Absolute)
                ];

            helper.Execute();
        }
    }

    /// <summary>
    /// ADC $AAAA,X - ADD using Absolute X Addressing
    /// </summary>
    class ADC_A_X : Opcode
    {
        ADCUtility helper = null;

        public ADC_A_X()
        {
            helper = new ADCUtility();
            this.CpuCycles = 4;
            this.AddressType = CPU.AddressType.AbsoluteIndexedX;
        }

        public override void Execute()
        {
            helper.Argument =
                CPU.Memory
                [
                    MemoryHelper.GetAddress
                    ((Int16)this.Argument,
                    CPU.AddressType.AbsoluteIndexedX)
                ];

            helper.Execute();
        }
    }

    /// <summary>
    /// ADC $AAAA,Y - ADD using Absolute Y Addressing
    /// </summary>
    class ADC_A_Y : Opcode
    {
        ADCUtility helper = null;

        public ADC_A_Y()
        {
            helper = new ADCUtility();
            this.CpuCycles = 4;
            this.AddressType = CPU.AddressType.AbsoluteIndexedY;
        }

        public override void Execute()
        {
            helper.Argument =
                CPU.Memory
                [
                    MemoryHelper.GetAddress
                    ((Int16)this.Argument,
                    CPU.AddressType.AbsoluteIndexedY)
                ];

            helper.Execute();
        }
    }

    /// <summary>
    /// ADC ($AA),Y - ADD using Indirect_Index Addressing
    /// </summary>
    class ADC_Indirect_Index : Opcode
    {
        ADCUtility helper = null;

        public ADC_Indirect_Index()
        {
            helper = new ADCUtility();
            this.CpuCycles = 5;
            this.AddressType = CPU.AddressType.IndirectIndexed;
        }

        public override void Execute()
        {
            helper.Argument =
                CPU.Memory
                [
                    MemoryHelper.GetAddress
                    ((Int16)this.Argument,
                    CPU.AddressType.IndirectIndexed)
                ];

            helper.Execute();
        }
    }

    /// <summary>
    /// ADC ($AA,X) - ADD using Index Indirect Addressing
    /// </summary>
    class ADC_Index_Indirect : Opcode
    {
        ADCUtility helper = null;

        public ADC_Index_Indirect()
        {
            helper = new ADCUtility();
            this.CpuCycles = 6;
            this.AddressType = CPU.AddressType.IndexedIndirect;

        }

        public override void Execute()
        {
            helper.Argument =
                CPU.Memory
                [
                    MemoryHelper.GetAddress
                    ((Int16)this.Argument,
                    CPU.AddressType.IndexedIndirect)
                ];

            helper.Execute();
        }
    }

    /// <summary>
    /// Does the actual ADC operation
    /// </summary>
    public class ADCUtility 
    {
        public Int16 Argument;

        public void Execute()
        {
            Int16 data = (Int16)this.Argument;
            data += (byte)(CPU.Registers.A + ((CPU.Registers.P.C) ? 1 : 0));

            // Set Zero Flag if needed
            CPU.Registers.P.Z = (data == 0);

            // If Decimal Mode
            if (CPU.Registers.P.D)
            {
                if ((CPU.Registers.A & 0xf) + (this.Argument & 0xf) + ((CPU.Registers.P.C) ? 1 : 0) > 9)
                    data += 6;
                
                if (data > 0x99)
                    data += 96;

                // Set Carry Flag
                CPU.Registers.P.C = (data > 0x99);
            }
            else
            {
                // Set Carry Flag
                CPU.Registers.P.C = (data > 0xff);
            }
            // Set the Negative Flag
            CPU.Registers.P.N = (data < 0);

            // Set Overflow Flag            
            CPU.Registers.P.V = !(((CPU.Registers.A ^ this.Argument) & 0x80) != 0 ) && (((CPU.Registers.A ^ data) & 0x80) != 0);

            CPU.Registers.A = (byte)data;
        }
    }
}
